<template>
  <div class="virtual-container" @scroll.passive="handleScroll" ref="container">
    <!-- 可视区域表格 -->
    <el-table
      :data="visibleData"
      :height="tableHeight"
      :row-height="rowHeight"
      :border="true"
      style="position: absolute; top: 0; width: 100%"
      :style="{ transform: `translateY(${offsetTop}px)` }"
    >
      <el-table-column
        v-for="col in columns"
        :key="col.prop"
        :prop="col.prop"
        :label="col.label"
        :min-width="col.width || 120"
      />
    </el-table>

    <!-- 滚动占位元素 -->
    <div class="scroll-placeholder" :style="{ height: totalHeight + 'px' }" />

    <!-- 加载状态 -->
    <div v-if="loading" class="loading-mask">
      <el-icon class="is-loading"><Loading /></el-icon>
    </div>
  </div>
</template>

<script>
import dataWorker from 'worker-loader!../dataWorker.worker.js';
export default {
  name: "VirtualTable",
  props: {
    data: {
      type: Array,
      required: true,
    },
    columns: {
      type: Array,
      required: true,
    },
    rowHeight: {
      type: Number,
      default: 48,
    },
    bufferRows: {
      type: Number,
      default: 10,
    },
  },

  data() {
    return {
      visibleData: [], // 当前显示的数据
      offsetTop: 0, // 表格位移
      startIndex: 0, // 起始索引
      totalHeight: 0, // 总高度
      tableHeight: 600, // 可视区域高度
      loading: false,
    };
  },

  mounted() {
    this.initializeWorker();
    this.calculateTableHeight();
    window.addEventListener("resize", this.onResize);
  },

  beforeUnmount() {
    this.worker.terminate();
    window.removeEventListener("resize", this.onResize);
  },

  watch: {
    data: {
      handler(newVal) {
        this.initializeWorker();
        this.worker.postMessage({ action: "init", data: newVal });
      },
      deep: true,
    },
  },

  methods: {
    /* 初始化 Web Worker */
    initializeWorker() {
      if (this.worker) this.worker.terminate();

      this.worker = new dataWorker();
      this.worker.onmessage = (e) => {
        const { type, data } = e.data;
        switch (type) {
          case "DATA_CHUNK":
            this.visibleData = Object.freeze(data);
            this.loading = false;
            break;
          case "INIT_DONE":
            this.calculateTotalHeight();
            break;
        }
      };
    },

    /* 计算容器高度 */
    calculateTableHeight() {
      this.tableHeight = this.$refs.container.clientHeight;
    },

    /* 计算总高度 */
    calculateTotalHeight() {
      this.totalHeight = this.data.length * this.rowHeight;
    },

    /* 滚动处理 */
    handleScroll(e) {
      const scrollTop = e.target.scrollTop;
      const visibleRows = Math.ceil(this.tableHeight / this.rowHeight);

      // 计算新的起止位置
      const newStart = Math.max(
        0,
        Math.floor(scrollTop / this.rowHeight) - this.bufferRows
      );

      const newEnd = Math.min(
        this.data.length,
        newStart + visibleRows + this.bufferRows * 2
      );

      if (newStart !== this.startIndex || newEnd !== this.endIndex) {
        this.loading = true;
        this.startIndex = newStart;
        this.endIndex = newEnd;
        this.offsetTop = newStart * this.rowHeight;

        this.worker.postMessage({
          action: "getChunk",
          start: newStart,
          end: newEnd,
        });
      }
    },

    /* 窗口大小变化处理 */
    onResize() {
      this.calculateTableHeight();
      this.$nextTick(() => this.handleScroll({ target: this.$refs.container }));
    },
  },
};
</script>

<style scoped>
.virtual-container {
  height: 100%;
  overflow: auto;
  position: relative;
}

.scroll-placeholder {
  position: relative;
  z-index: -1;
}

.loading-mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
